package com.sojson.config.qps.sentinel.util;

import java.io.IOException;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.sojson.config.exception.TransException;
import com.sojson.config.qps.sentinel.aspect.ASentinelAspectSupport;
import com.sojson.result.Result;
import com.sojson.util.servlet.ServletUtil;

/**
 * Sentine限流工具类
 * 
 * @author liu
 * @date 2021-08-25
 */
public class SentinelUtil {

    /**
     * 返回错误信息
     * 
     * @param e
     * @throws IOException 
     */
    public static void throwException(BlockException e) throws IOException {
        throw getException(e);
    }

    /**
     * 输出数据
     * 
     * @param e
     * @throws IOException 
     */
    public static void out(BlockException e) throws IOException {
        out(ServletUtil.getHttpServletRequest(), ServletUtil.getHttpServletResponse(), e);
    }

    /**
     * 输出数据
     * 
     * @param request
     * @param response
     * @param e
     * @throws IOException 
     */
    public static void out(HttpServletRequest request, HttpServletResponse response, BlockException e)
        throws IOException {
        ServletUtil.out(request, response, Result.fail(getException(e)));
    }

    /**
     * 输出数据
     * 
     * @param e
     * @throws IOException 
     */
    public static TransException getException(BlockException e) throws IOException {
        String msg = null;

        // 不同的异常返回不同的提示语
        // if (e instanceof FlowException) {
        // msg = "被限流了";
        // } else if (e instanceof DegradeException) {
        // msg = "服务降级了";
        // } else if (e instanceof ParamFlowException) {
        // msg = "被限流了";
        // } else if (e instanceof SystemBlockException) {
        // msg = "被系统保护了";
        // } else if (e instanceof AuthorityException) {
        // msg = "已被拉进黑名单";
        // }
        if (e instanceof AuthorityException) {
            msg = "已被拉进黑名单";
        } else {
            msg = "操作繁忙,请稍后再试";
        }

        return new TransException(msg);
    }

    /**
     * 切面处理逻辑
     * 
     * @param pjp                       环绕通知
     * @param sentinelAspect            切面
     * @param defaultSentinelResource   默认注解(如果方法上没有)
     * @return
     * @throws Throwable
     */
    public static Object invokeResourceWithSentinel(ProceedingJoinPoint pjp, ASentinelAspectSupport sentinelAspect,
        SentinelResource defaultSentinelResource) throws Throwable {
        Method originMethod = sentinelAspect.resolveMethod(pjp);

        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
            // Should not go through here.
            // TODO
            // throw new IllegalStateException("Wrong state for SentinelResource annotation");
            annotation = defaultSentinelResource;
        }
        String resourceName = sentinelAspect.getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
            Object result = pjp.proceed();
            return result;
        } catch (BlockException e) {
            // TODO
            // return handleBlockException(pjp, annotation, e);
            SentinelUtil.throwException(e);
            return null;
        } catch (Throwable ex) {
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && sentinelAspect.exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (sentinelAspect.exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                sentinelAspect.traceException(ex, annotation);
                return sentinelAspect.handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }

}